package com.taotao.nio;

//import org.junit.Test;

import org.junit.Test;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.security.SecureRandom;
import java.sql.Time;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Description: nio 随机数测试
 * @Author: TaoJF
 * @Date: Created in 2018/7/1 下午8:59
 */
public class NioTest {

    @Test
    public void NioRandNumTest() {
        IntBuffer intBuffer = IntBuffer.allocate(10);
        for (int i = 0; i < intBuffer.capacity(); i++) {
            int randNum = new SecureRandom().nextInt(20);
            intBuffer.put(randNum);
        }

        intBuffer.flip(); //翻转，输入转为输出；

        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }

    @Test
    public void NioFileReadTest() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("src/main/java/com/taotao/nio/file/NioReadFileTest2.txt");
        FileChannel fileChannel = fileInputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        fileChannel.read(byteBuffer);

        byteBuffer.flip();

        while (byteBuffer.remaining() > 0) {
            byte b = byteBuffer.get();
            System.out.println("Character：" + (char) b);
        }

        fileInputStream.close();
    }

    @Test
    public void NioFileWriteTest() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("src/main/java/com/taotao/nio/file/NioFIleWriteTest.txt");
        FileChannel fileChannel = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        byte[] messages = "hello world, everyone!".getBytes();

        for (int i = 0; i < messages.length; i++) {
            byteBuffer.put(messages[i]);
        }

        byteBuffer.flip();

        fileChannel.write(byteBuffer);

        fileOutputStream.close();
    }

    @Test
    public void NioFileReadAndWriteTest() throws IOException, InterruptedException {
        FileInputStream fileInputStream = new FileInputStream("src/main/java/com/taotao/nio/file/NioReadFileTest4.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("src/main/java/com/taotao/nio/file/NioWriteFileTest4.txt");
        FileChannel inputChannel = fileInputStream.getChannel();
        FileChannel outputChannel = fileOutputStream.getChannel();

//        ByteBuffer byteBuffer = ByteBuffer.allocate(14);
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while (true) {
            byteBuffer.clear(); //注释与不注释的区别；

            int readNum = inputChannel.read(byteBuffer);
            System.out.println("当前读取到 byteBuffer 中的字节数：" + readNum);

            if (readNum == -1) {
                break;
            }

            byteBuffer.flip();

            outputChannel.write(byteBuffer);
            TimeUnit.SECONDS.sleep(1);
        }

        fileInputStream.close();
        fileOutputStream.close();
    }

    //类型化put和get方法，按照放入的类型规则取，可以自定义协议，类似java字节码文件读取；
    @Test
    public void NioTypePutAndGetTest() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);

        byteBuffer.putInt(10);
        byteBuffer.putLong(24);
        byteBuffer.putDouble(10.24);
        byteBuffer.putChar('我');
        byteBuffer.putShort((short) 3);
        byteBuffer.putChar('你');

        byteBuffer.flip();

        System.out.println(byteBuffer.getInt());
        System.out.println(byteBuffer.getLong());
        System.out.println(byteBuffer.getDouble());
        System.out.println(byteBuffer.getChar());
        System.out.println(byteBuffer.getShort());
//        System.out.println(byteBuffer.getInt()); //java.nio.BufferUnderflowException
        System.out.println(byteBuffer.getChar());
    }

    //Slice Buffer和原有buffer共享底层存储的数组；
    @Test
    public void NioSliceBufferTest() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        for (int i = 0; i < byteBuffer.capacity(); i++) {
            System.out.print(i + " ");
            byteBuffer.put((byte) i);
        }
        System.out.println("----");

        //设置位置用于截取；
        byteBuffer.position(2);
        byteBuffer.limit(6);

        //分片的截取位置为(postion, limit]，含头不含尾；
        ByteBuffer sliceByteBuffer = byteBuffer.slice();

        for (int i = 0; i < sliceByteBuffer.capacity(); i++) {
            byte b = sliceByteBuffer.get(i);
            sliceByteBuffer.put((byte) (b * 2));
        }

        //从头开始读取；
        byteBuffer.clear();

        for (int i = 0; i < byteBuffer.capacity(); i++) {
            System.out.print(byteBuffer.get(i) + " ");
        }
    }

    //只读buffer，可以将一个读/写buffer调用asReadOnlyBuffer转为只读buffer，但是无法将一个只读buffer转为读/写buffer，抛异常；
    @Test
    public void NioOnlyReadBufferTest() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
//        ByteBuffer direct = ByteBuffer.allocateDirect(10); //直接内存字节缓冲
        System.out.println("byteBuffer的class：" + byteBuffer.getClass());

        for (int i = 0; i < byteBuffer.capacity(); i++) {
            System.out.println(String.format("byteBuffer的position：%s，limit：%s, capacity：%s", byteBuffer.position(), byteBuffer.limit(), byteBuffer.capacity()));
            byteBuffer.put((byte) i);
        }
        //转为只读buffer
        ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
        System.out.println("readOnlyBuffer的class：" + readOnlyBuffer.getClass());

        System.out.println(String.format("byteBuffer的position：%s，limit：%s, capacity：%s", byteBuffer.position(), byteBuffer.limit(), byteBuffer.capacity()));
        System.out.println(String.format("readOnlyBuffer的position：%s，limit：%s，capacity：%s", readOnlyBuffer.position(), readOnlyBuffer.limit(), readOnlyBuffer.capacity()));

        System.out.println("==> 调用flip()翻转;");
//        byteBuffer.flip();
        readOnlyBuffer.flip();

        System.out.println(String.format("byteBuffer的position：%s，limit：%s, capacity：%s", byteBuffer.position(), byteBuffer.limit(), byteBuffer.capacity()));
        System.out.println(String.format("readOnlyBuffer的position：%s，limit：%s，capacity：%s", readOnlyBuffer.position(), readOnlyBuffer.limit(), readOnlyBuffer.capacity()));

        for (int i = 0; i < readOnlyBuffer.capacity(); i++) {
            System.out.print(readOnlyBuffer.get() + " ");
        }

//        readOnlyBuffer.put((byte) 9); //方法内直接抛异常！

    }

    @Test
    public void NioMappedByteBufferTest() throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("src/main/java/com/taotao/nio/file/NioMappedByteBufferTest.txt", "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();

        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        mappedByteBuffer.put(0, (byte) 'T');
        mappedByteBuffer.put(2, (byte) 'J');

        randomAccessFile.close();
        System.out.println("执行完毕，并关闭管道流！");
    }

    @Test
    public void NioFileLockTest() throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("src/main/java/com/taotao/nio/file/NioMappedByteBufferTest.txt", "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();

        FileLock fileLock = fileChannel.lock(3, 6, true);

        System.out.println("valid: " + fileLock.isValid() + ", lock type：" + fileLock.isShared());

        fileLock.release();
        randomAccessFile.close();
    }

    //mac上用nc localhost 8899测试，还可以用telent
    //关于Buffer的Scattering与Gathering的含义
    @Test
    public void NioMultiByteBuffersTest() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress socketAddress = new InetSocketAddress(8899);
        serverSocketChannel.socket().bind(socketAddress);
        System.out.println("启动服务器端，开始监听...");

        int messageLength = 2 + 3 + 4;

        ByteBuffer[] byteBuffers = new ByteBuffer[3];
        byteBuffers[0] = ByteBuffer.allocate(2);
        byteBuffers[1] = ByteBuffer.allocate(3);
        byteBuffers[2] = ByteBuffer.allocate(4);

        SocketChannel socketChannel = serverSocketChannel.accept();

        while (true) {
            System.out.println("==> 当前线程：" + Thread.currentThread().getName());
            int bytesRead = 0;
            while (bytesRead < messageLength) {
                long read = socketChannel.read(byteBuffers);
                bytesRead += read;
                System.out.println("bytesRead：" + bytesRead);

                Arrays.asList(byteBuffers).stream().
                        map(byteBuffer -> "position：" + byteBuffer.position() + "，limit：" + byteBuffer.limit()).
                        forEach(System.out::println);
            }

            Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.flip());

            long bytesWritten = 0;
            while (bytesWritten < messageLength) {
                long writeNum = socketChannel.write(byteBuffers);
                bytesWritten += writeNum;
            }

            Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.clear());

            System.out.println("bytesRead：" + bytesRead + "，bytesWritten：" + bytesWritten + "，messageLength：" + messageLength);
        }
    }


    @Test
    public void NioSelectorTest() throws IOException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        int[] ports = new int[5];

        ports[0] = 5000;
        ports[1] = 5001;
        ports[2] = 5002;
        ports[3] = 5003;
        ports[4] = 5004;

        Selector selector = Selector.open();

        for (int i = 0; i < ports.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            ServerSocket serverSocket = serverSocketChannel.socket();

            InetSocketAddress inetSocketAddress = new InetSocketAddress(ports[i]);
            serverSocket.bind(inetSocketAddress);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("监听端口：" + ports[i]);

//            Set<SelectionKey> selectionKeys = selector.selectedKeys();
//            System.out.println(selectionKeys);
        }

        while (true) {
            int selectNums = selector.select();
            System.out.println("--> selectNums：" + selectNums);
//            TimeUnit.SECONDS.sleep(10);
//            System.out.println("睡眠10秒，结束");

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("1. selectionKeys：" + selectionKeys + "，selectionKeys的大小：" + selectionKeys.size());

            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    System.out.println("2. 进入selectionKey.isAcceptable()方法...");
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);

                    socketChannel.register(selector, SelectionKey.OP_READ);
                    //System.out.println("2. selectionKeys：" + selectionKeys + "，selectionKeys的大小：" + selectionKeys.size());
                    executorService.submit(() -> {
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("2.1 thread ==> 模拟进行业务处理操作，睡眠10秒，结束");
                        System.out.println("2.2 获取客户端连接：" + socketChannel);
                        System.out.println("2.3 selectionKeys：" + selectionKeys + "，selectionKeys的大小：" + selectionKeys.size());
                    });
                    iterator.remove();
                } else if (selectionKey.isReadable()) {
                    System.out.println("3. 进入selectionKey.isReadable()方法...");
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    int byteReadNum = 0;
                    try {
                        while (true) {
                            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                            byteBuffer.clear();
                            int readNum = socketChannel.read(byteBuffer);
                            if (readNum <= 0) {
                                break;
                            }
                            byteReadNum += readNum;
                            System.out.println("3.2 读取：" + byteReadNum + "，来自于：" + socketChannel);

                            //模拟业务处理
                            executorService.submit(() -> {
                                try {
                                    System.out.println("3.1 thread ==> 模拟进行业务处理操作，睡眠2秒，执行结束");
                                    TimeUnit.SECONDS.sleep(2);
                                    byteBuffer.flip();
                                    socketChannel.write(byteBuffer);
                                    System.out.println("3.2 向客户端写出完毕！");
                                } catch (InterruptedException | IOException e) {
                                    e.printStackTrace();
                                }
                            });
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
//                    iterator.remove();
                    System.out.println("--> 执行 iterator.remove(); ");
                    System.out.println();
                }
            }

        }

    }


    //字符集编码测试；
    @Test
    public void decoderAndEncoderTest() throws IOException {
        String inputFile = "src/main/java/com/taotao/nio/file/decoderAndEncoderInputTest.txt";
        String outputFile = "src/main/java/com/taotao/nio/file/decoderAndEncoderOutputTest.txt";

        RandomAccessFile inputAccessFile = new RandomAccessFile(inputFile, "r");
        RandomAccessFile outputAccessFile = new RandomAccessFile(outputFile, "rw");

        long inputLength = new File(inputFile).length();

        FileChannel inputFileChannel = inputAccessFile.getChannel();
        FileChannel outputFileChannel = outputAccessFile.getChannel();

        MappedByteBuffer mappedByteBuffer = inputFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputLength);

//        System.out.println("===> ");
//        Charset.availableCharsets().forEach((k, v) -> System.out.println(k + ": " + v));
//        System.out.println("<===");

        Charset charset = Charset.forName("iso-8859-1");
//        Charset charset = Charset.forName("GBK");
        CharsetDecoder decoder = charset.newDecoder();
        CharsetEncoder encoder = charset.newEncoder();

        CharBuffer charBuffer = decoder.decode(mappedByteBuffer);

        System.out.println(charBuffer.get(14));
        System.out.println(charBuffer.get(15));
        //iso-8859-1默认按一个字节为一个字符，而utf8对中文为3个字节为一个字符；
        System.out.println(charBuffer.get(16));
        System.out.println("--->");
        System.out.println(charBuffer.toString());

        //因为文件显示为utf8，所以最终中文仍可以显示而不出现乱码；
//        ByteBuffer outputBuffer = encoder.encode(charBuffer);
        //但编码不用 iso-8859-1的话，必然存储显示会出现乱码；会以 iso-8859-1 编码的一个字节当做一个字符，从而中文乱码；
        //所以编码和解码要一致，防止出现乱码；
        //乱码还和当前文件的编码格式有关；
//        ByteBuffer outputBuffer = Charset.forName("utf-8").encode(charBuffer);
        ByteBuffer outputBuffer = Charset.forName("iso-8859-1").encode(charBuffer);

        outputFileChannel.write(outputBuffer);

        inputAccessFile.close();
        outputAccessFile.close();
    }

    public static void main(String[] args) {
        
    }

}
